package com.chouren.goandroid.img;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.widget.ImageView;

import com.chouren.goandroid.util.ImageUtil;

/**
 * Load image from network and add it to cache for recyle use.
 * @author TangLong
 * @date 2014年8月12日
 */
public class ImageLoader {
	private static final String TAG = "ImageLoader";
	private boolean debug;
	private MemoryCache memoryCache;
	private FileCache fileCache;
	private ExecutorService executorService;
	private Map<ImageView, String> imageViews = Collections
			.synchronizedMap(new WeakHashMap<ImageView, String>());

	public ImageLoader(Context context) {
		debug = false;
		fileCache = new FileCache(context);
		memoryCache = new MemoryCache();
		executorService = Executors.newFixedThreadPool(1);
	}
	
	/**
	 * load image from network or cache
	 * 
	 * @param url
	 *            the url of the image
	 * @param imageView
	 *            the imageview that show the image
	 * @param width
	 *            require width of imagview
	 * @param height
	 *            require height of imageview
	 * @param isOnlyLoadFromCache
	 *            if you want to load the image only from the cache, set it true
	 */
	public void loadImage(String url, ImageView imageView, int width, int height, boolean isOnlyLoadFromCache) {
		if(debug) Log.d(TAG, "load image url:" + url);
		Bitmap bitmap = memoryCache.getCache(url);

		if (bitmap != null) {
			if(debug) Log.d(TAG, "load from memory");
			imageView.setImageBitmap(bitmap);
		} else {
			if(debug) Log.d(TAG, "load from dir");
			bitmap = ImageUtil.decodeSampledBitmapFromFile(fileCache.getCacheFileAbsolutePath(url), width, height);
		}

		if (bitmap == null && !isOnlyLoadFromCache) {
			ImageLoad imageLoad = new ImageLoad(url, imageView);
			if (needStartNewThread(imageLoad)) {
				if(debug) Log.d(TAG, "load from net");
				newThreadToLoadImage(imageLoad, width, height);
			} else {
				if(debug) Log.d(TAG, "image is loading, cancel, url:" + url);
			}
			imageViews.put(imageView, url);
		} else {
			imageView.setImageBitmap(bitmap);
		}
	}

	/**
	 * load image from network or cache
	 * 
	 * @param url
	 *            the url of the image
	 * @param imageView
	 *            the imageview that show the image
	 * @param isOnlyLoadFromCache
	 *            if you want to load the image only from the cache, set it true
	 */
	public void loadImage(String url, ImageView imageView, boolean isOnlyLoadFromCache) {
		if(debug) Log.d(TAG, "load image url:" + url);
		Bitmap bitmap = memoryCache.getCache(url);

		if (bitmap != null) {
			if(debug) Log.d(TAG, "load from memory");
			imageView.setImageBitmap(bitmap);
		} else {
			bitmap = ImageUtil.decodeSampledBitmapFromFile(
					fileCache.getCacheFileAbsolutePath(fileCache.getCacheFileAbsolutePath(url)));
		}

		if (bitmap == null && !isOnlyLoadFromCache) {
			ImageLoad imageLoad = new ImageLoad(url, imageView);
			if (needStartNewThread(imageLoad)) {
				if(debug) Log.d(TAG, "load from net");
				newThreadToLoadImage(imageLoad, 0, 0);
			} else {
				if(debug) Log.d(TAG, "image is loading, cancel, url:" + url);
			}
			imageViews.put(imageView, url);
		} else {
			imageView.setImageBitmap(bitmap);
		}
	}

	/**
	 * start a new thread to load the image
	 */
	private void newThreadToLoadImage(ImageLoad imageLoad, int width, int height) {
		executorService.submit(new ImageLoadTask(imageLoad, width, height));
	}

	/**
	 * check if need to start a new thread to load image
	 * 
	 * @param imageLoad
	 *            the ImageLoad object
	 * @return if the image url has been loaded, return false
	 */
	private boolean needStartNewThread(ImageLoad imageLoad) {
		boolean result = true;
		ImageView iv = imageLoad.getImageView();
		if (iv != null) {
			String url = imageViews.get(iv);
			if (url != null && url.equals(imageLoad.getUrl())) {
				result = false;
			}
		}
		return result;
	}

	/**
	 * set if enable the dubug model or not
	 */
	public ImageLoader setDebug(boolean enable) {
		this.debug = enable;
		return this;
	}

	/**
	 * The task to load image from network
	 */
	private class ImageLoadTask implements Runnable {
		private ImageLoad imageLoad;
		private int width;
		private int height;

		public ImageLoadTask(ImageLoad imageLoad, int width, int height) {
			this.imageLoad = imageLoad;
			this.width = width;
			this.height = height;
		}

		@Override
		public void run() {
			try {
				Bitmap bitmap = null;
				if (!fileCache.createCacheFile(imageLoad.getUrl())) {
					if(debug) Log.d(TAG, "create cache file fail:" + imageLoad.getUrl());
					return;
				}
				File f = fileCache.getCacheFile(imageLoad.getUrl());
				
				// download image
				URL imageUrl = new URL(imageLoad.getUrl());
				HttpURLConnection conn = (HttpURLConnection) imageUrl
						.openConnection();
				conn.setConnectTimeout(30000);
				conn.setReadTimeout(30000);
				conn.setInstanceFollowRedirects(true);
				InputStream is = conn.getInputStream();
				OutputStream os = new FileOutputStream(f);
				copyStream(is, os);
				
				if(width == 0 || height == 0) {
					bitmap = ImageUtil.decodeSampledBitmapFromFile(
							fileCache.getCacheFileAbsolutePath(imageLoad.getUrl()));
				}else {
					bitmap = ImageUtil.decodeSampledBitmapFromFile(
							fileCache.getCacheFileAbsolutePath(imageLoad.getUrl()), width, height);
				}
				
				// put image in memory cache and show the image
				memoryCache.put(imageLoad.getUrl(), bitmap);
				Activity activity = (Activity) imageLoad.getImageView()
						.getContext();
				activity.runOnUiThread(new BitmapDisplayTask(bitmap, imageLoad));
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}

		/**
		 * copy stream
		 * @param is
		 * @param os
		 */
		public void copyStream(InputStream is, OutputStream os) {
			final int buffer_size = 1024;
			try {
				byte[] bytes = new byte[buffer_size];
				for (;;) {
					int count = is.read(bytes, 0, buffer_size);
					if (count == -1)
						break;
					os.write(bytes, 0, count);
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				if(is != null) {
					try {
						is.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
				if(os != null) {
					try {
						os.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
	
	/**
	 * task to show the image
	 */
	class BitmapDisplayTask implements Runnable {
		private Bitmap bitmap;
		private ImageLoad imageLoad;

		public BitmapDisplayTask(Bitmap bitmap, ImageLoad imageLoad) {
			this.bitmap = bitmap;
			this.imageLoad = imageLoad;
		}

		@Override
		public void run() {
			if (bitmap != null) {
				imageLoad.getImageView().setImageBitmap(bitmap);
			}
		}
	}
	
	/**
	 * listener on image load finished
	 */
	public interface OnImageDownloadListener {
		public void onImageDownloadFinish(String url, Bitmap bitmap);
	}
}
